#pragma once
#include <iostream>
#include <string>
#include <ctime>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdarg>

enum Level
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal
};

enum Way
{
    Screen,
    OneFile,
    ClassifyFile
};

// 不把他设计成是int类型是因为：设置为int类型的话，那么就可以随意传任何数字，
// 但是设计成是Way类型，就只能传Way枚举类型结构中有的内容了
static const enum Way defaultway = Screen;
static const std::string defaultfilename = "log.";
static const std::string filepath = "log";

class Log
{
public:
    Log(enum Way way = defaultway) : _way(way) {}

public:
    void LogMessage(Level level, const char *format, ...)
    {
        // 构建日志等级字符串
        std::string levelstr = LevelToString(level);
        // 构建日志日期字符串
        std::string timestr = TimeToString();
        // 构建进程id的字符串
        std::string pidstr = std::to_string(getpid());

        // 把不定参数的日志内容构建成字符串
        char right_buffer[1024];
        va_list args;           // 声明一个变量，va_list是一个类型
        va_start(args, format); // 初始化args变量
        // va_arg：用于从参数列表中检索下一个参数, 在vsnprintf中会使用该宏，进行遍历不定参数的内容
        vsnprintf(right_buffer, sizeof(right_buffer), format, args);
        va_end(args); // 清空args，也就是args=nullptr

        // 将日志等级、时间、pid构建成一个字符串
        char left_buffer[256];
        snprintf(left_buffer, sizeof(left_buffer), "[%s][%s][%s]",
                 levelstr.c_str(), timestr.c_str(), pidstr.c_str());

        std::string message = left_buffer;
        message += right_buffer;
        WriteLog(levelstr, message);
    }

private:
    std::string LevelToString(const Level level)
    {
        switch (level)
        {
        case Debug:
            return "Debug";
        case Info:
            return "Info";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "The Level is undefined!";
        }
    }
    std::string TimeToString()
    {
        // 获取当前时间
        time_t now = time(nullptr);
        // 将time_t类型的时间转换为tm结构体的时间
        struct tm *t = localtime(&now);
        // 但这个时间是基于时间戳累计的时间，也就是在1970年1月1日0:0:0开始算的，所以要加上这个时间
        // snprintf函数，可以用来构建不定参数的内容的字符串
        // vsnprintf函数则是配合va_list进行构建不定参数内容的字符串
        char time_buffer[128];
        snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
                 t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
                 t->tm_hour, t->tm_min, t->tm_sec);

        return time_buffer;
    }
    void WriteLog(const std::string &levelstr, const std::string &message)
    {
        if (_way == Screen)
            std::cout << message;
        else if (_way == OneFile)
            WriteLogToFile("all", message);
        else if (_way == ClassifyFile)
            WriteLogToFile(levelstr, message);
    }
    void WriteLogToFile(const std::string &logname, const std::string &message)
    {
        // 该修改只是对于该进程以及子进程进行修改，不是系统级别的，也不是整个会话级别
        umask(0);
        mkdir(filepath.c_str(), 0775);

        std::string filename = filepath;
        filename += "/";
        filename += defaultfilename;

        std::string name = logname;
        name[0]= std::tolower(logname[0]);
        filename += name;

        int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0664);
        write(fd, message.c_str(), message.size());
        close(fd);
    }

private:
    int _way;
};

/**
 * 思路：
 * 首先：构建简单的将日志信息打印到屏幕上
 *
 * 外部通过调用LogMessage函数进行日志打印的操作，函数传递参数为日志等级和不定参数的日志内容
 * 在LogMessage函数内部，通过switch/case语句把枚举类型等级的值转换为字符串类型，
 * 之后在构建时间和pid的字符串，构建后用snprintf把这3个内容合起来（left_buffer）
 *
 * 之后构建用户传递的日志内容字符串（right_buffer），采用vsnprintf函数构建
 * 最后把两者合并一下即可
 *
 * 完善功能：
 * (1) 选择打印到屏幕还是文件,同时传入打印到文件的文件名称
 * (2) 根据日志等级，自动打印到不同的文档中
 */